package edu.hawaii.wattwaiter.wattdepotcommands;

import java.util.ArrayList;
import java.util.List;
import javax.xml.datatype.DatatypeConstants;
import javax.xml.datatype.XMLGregorianCalendar;
import org.wattdepot.util.tstamp.Tstamp;

/**
 * Creates a Google Chart with the information provided. <br />
 * <br />
 * Creating a URL for a Google Chart for carbon, from the duration between October 25th, 2009 and
 * October 26th, 2009. For the simulated Oahu grid, for each hour. Without transitive or cumulative
 * results.
 * 
 * <pre>
 * GoogleChartMaker chartMaker = new GoogleChartMaker(null);
 * String result =
 *     chartMaker
 *         .getChart(&quot;carbon&quot;, &quot;2009-10-25&quot;, &quot;2009-10-26&quot;, &quot;SIM_OAHU_GRID&quot;, &quot;hour&quot;, false, false);
 * </pre>
 * 
 * @author David Mau
 */
public class GoogleChartMaker {
  /** The WattDepot server to use to collect information from. */
  private static ChartDataCollector clientData = new ChartDataCollector(null);
  /** Image URL to return if an unexpected error occurs. */
  static String errorImage = "http://www2.hawaii.edu/~maud/UnexpectedError.png";
  /** Image URL to return if a time based error occurs. */
  static String timeImage = "http://www2.hawaii.edu/~maud/TimeError.JPG";
  /** Google Chart's current maximum URL size in characters. */
  static int maxUrlSize = 2048;
  /** Image URL to return if the URL returned is over 2048 characters. */
  static String overflowImage = "http://www2.hawaii.edu/~maud/OverflowError.JPG";
  /** A List of colors to use on the graph. */
  private static List<String> graphColorList = new ArrayList<String>();

  /**
   * Establishes a connection to the WattDepot server to collect information from. If given null,
   * uses the default server.
   * 
   * @param alternateServer Another WattDepot server to connect to. If null, uses the default
   * server.
   */
  public GoogleChartMaker(String alternateServer) {
    // If another server was given, set it to be the server.
    if (alternateServer != null) {
      setNewServer(alternateServer);
    }

    // Initialize the color list.
    graphColorList.add("000000");
    graphColorList.add("990033");
    graphColorList.add("FF9900");
    graphColorList.add("FFCC00");
    graphColorList.add("66CC66");
    graphColorList.add("00CCFF");
    graphColorList.add("6633FF");
    graphColorList.add("999999");
    graphColorList.add("CC6600");
    graphColorList.add("990099");
    graphColorList.add("3366CC");
    graphColorList.add("333333");
    graphColorList.add("FF3399");
    graphColorList.add("006666");
    graphColorList.add("999933");
    graphColorList.add("666666");
    graphColorList.add("6699CC");
    graphColorList.add("009999");
    graphColorList.add("3333CC");
    graphColorList.add("990099");

  }

  /**
   * Changes the current WattDepot server to connect to.
   * 
   * @param alternateServer The WattDepot server to change to.
   */
  private void setNewServer(String alternateServer) {
    clientData = new ChartDataCollector(alternateServer);
  }

  /**
   * Creates the Google Charts URL for use in a web page.
   * 
   * @param carbonOrEnergy Must be either "carbon" or "energy". Queries the WattDepot server using
   * the appropriate command depending on this choice.
   * @param startDay The day to start collecting information from. Form of the date should be in
   * YYYY-MM-DD, where Y is for years, M is for months, and D is for days.
   * @param endDay The day to stop collecting information at. The information will not include this
   * day. Form of the date should be in YYYY-MM-DD, where Y is for years, M is for months, and D is
   * for days.
   * @param source The source to collect the information from the server about.
   * @param timeFrame Must be either "hour", "day", or "week". Causes the returned <code>List</code>
   * to have data for every hour, day, or week in the time duration given.
   * @param transitive To chart data about the given source's all non-virtual sub-sources.
   * @param cumulative To chart the running total of all of the points placed on the graph.
   * @return A URL that may be used to display a Google Chart. If an error occurs, the error image
   * in the static variable of this class will be returned instead.
   */
  public String getChart(String carbonOrEnergy, String startDay, String endDay, String source,
      String timeFrame, boolean transitive, boolean cumulative) {
    String output = "";
    output = output.concat("http://chart.apis.google.com/chart?");
    output = output.concat("cht=lc");
    output = output.concat("&amp;chs=600x200");
    output = output.concat("&amp;chxt=x,y");

    String colorList = "&amp;chco=";
    int colorCounter = 0;
    boolean firstColor = true;

    String legendList = "&amp;chdl=";
    boolean firstLegend = true;

    String dataList = "&amp;chd=t:";
    boolean firstDataGroup = true;
    boolean firstData = true;

    Double currentMax = 0.0;

    List<Double> lastList = new ArrayList<Double>();
    int sourcesCounter = 0;

    /*
     * System.out.println( "DEBUG: carbonOrEnergy: " + carbonOrEnergy ); System.out.println(
     * "DEBUG: startDay: " + startDay ); System.out.println( "DEBUG: endDay: " + endDay );
     * System.out.println( "DEBUG: source: " + source ); System.out.println( "DEBUG: timeFrame: " +
     * timeFrame );
     * 
     * if ( transitive ) { System.out.println( "DEBUG: transitive: ON."); } else {
     * System.out.println( "DEBUG: transitive: OFF."); }
     * 
     * if ( cumulative ) { System.out.println( "DEBUG: cumulative: ON."); } else {
     * System.out.println( "DEBUG: cumulative: OFF."); }
     */

    // If the source given is null,
    if (source == null) {
      // Immediately return the error image.
      return errorImage;
    }

    // Create a List for sources that need to be graphed.
    List<String> sourcesToGraph = new ArrayList<String>();

    // If transitive results are wanted,
    if (transitive) {
      sourcesToGraph = clientData.getAllSubSources(source);
      // If an error occurred,
      if (sourcesToGraph == null) {
        // Immediately return the error image.
        return errorImage;
      }
    }
    // Otherwise, just add the source provided to the the List.
    else {
      sourcesToGraph.add(source);
    }

    // Get all of the data from WattDepot.
    for (String sourceToGraph : sourcesToGraph) {
      List<Double> rawData =
          clientData.getChartData(carbonOrEnergy, startDay, endDay, sourceToGraph, timeFrame);

      // The method getChartData will also check for any errors in the
      // parameters it is given, so this check will check the carbonOrEnergy,
      // startDay, endDay, sourceToGraph, and timeFrame parameters as well.
      // If an error occurred,
      if (rawData == null) {
        // Immediately return the error image.
        // With Remy's checks, the only real errors that can occur are server
        // errors and startDay and endDay errors.
        return timeImage;
      }

      // If this is the last list, save it.
      if (sourcesCounter == sourcesToGraph.size() - 1) {
        for (Double rawNumber : rawData) {
          lastList.add(rawNumber);
        }
      }

      // If this is not the first group of data to be graphed,
      if (!firstDataGroup) {
        // Add the preceding "|" to the data list.
        dataList = dataList.concat("|");
      }

      // Go through the list of data.
      for (Double rawNumber : rawData) {

        // If the data is for energy, divide by 1,000,000 for MegaWatts.
        if (carbonOrEnergy.compareTo("energy") == 0) {
          rawNumber = rawNumber / 1000000;
        }

        // Check the data to see if it is greater than the current maximum.
        if (rawNumber > currentMax) {
          // If it is, replace the current maximum with the number.
          currentMax = rawNumber;
        }

        // Add the data to the chart.
        // If this is the first data of the group,
        if (!firstData) {
          // Add the preceding ",".
          dataList = dataList.concat(",");
        }
        // Add the number to the data list.
        dataList = dataList.concat(String.format("%1$.0f", rawNumber));
        firstData = false;
      }

      // The next group of data will have it's own first member.
      firstData = true;
      // The next group of data will not be the first.
      firstDataGroup = false;

      // Add the source that was graphed to the legend list.
      // If this was not the first source to be added to the legend list,
      if (!firstLegend) {
        // Add the preceding "|".
        legendList = legendList.concat("|");
      }
      // Add the source to the legend list.
      legendList = legendList.concat(sourceToGraph);
      firstLegend = false;

      // Add the color that should be used to graph the source in.
      // If this was not the first color to be added to the color list,
      if (!firstColor) {
        // Add the preceding ",".
        colorList = colorList.concat(",");
      }
      // Add the right color to the color list.
      colorList = colorList.concat(graphColorList.get(colorCounter));
      colorCounter++;
      firstColor = false;

      sourcesCounter++;
    }

    // If cumulative data is needed,
    if (cumulative) {
      Double runningTotal = 0.0;
      boolean firstCumulative = true;

      dataList = dataList.concat("|");

      // Use the last list to calculate the cumulative graph.
      for (Double lastNumbers : lastList) {

        // If the data is for energy, divide by 1,000,000 for MegaWatts.
        if (carbonOrEnergy.compareTo("energy") == 0) {
          lastNumbers = lastNumbers / 1000000;
        }

        // Update the running total.
        runningTotal = runningTotal + lastNumbers;

        // If this is not the first cumulative list number, add a preceding ",".
        if (!firstCumulative) {
          dataList = dataList.concat(",");
        }
        // Add the number to the data list.
        dataList = dataList.concat(String.format("%1$.0f", runningTotal));
        firstCumulative = false;
      }

      // Add the cumulative to the legend.
      legendList = legendList.concat("|Cumulative");

      // Assign a color to the cumulative graph.
      colorList = colorList.concat("," + graphColorList.get(colorCounter));
      colorCounter++;

      // Set the current maximum value to the final value of the running total.
      currentMax = runningTotal;

    }

    // Add the legend to the chart.
    output = output.concat(legendList);
    // Add the colors for the sources to the chart.
    output = output.concat(colorList);

    // Add the labels to the chart.
    output = output.concat("&amp;chxl=");
    String numberLabels = "0:|";
    // Convert the starting date into an XML one.
    XMLGregorianCalendar xmlStartTime = null;
    try {
      xmlStartTime = Tstamp.makeTimestamp(startDay);
    }
    catch (Exception e) {
      // Should never happen, it was already checked when collecting Data.
      return errorImage;
    }

    // Convert the end date into an XML one.
    XMLGregorianCalendar xmlEndTime = null;
    try {
      xmlEndTime = Tstamp.makeTimestamp(endDay);
    }
    catch (Exception e) {
      // Should never happen, it was already checked when collecting Data.
      return errorImage;
    }

    // Make labels appropriate for the time frame
    while (xmlStartTime.compare(xmlEndTime) == DatatypeConstants.LESSER) {
      // Append appropriate labels for each time frame.
      if (timeFrame.compareTo("hour") == 0) {
        numberLabels =
            numberLabels.concat("Mid.|1|2|3|4|5|6|7|8|9|10|11|Noon|1|2|3|4|5|6|7|8|9|10|11|");

        xmlStartTime = Tstamp.incrementDays(xmlStartTime, 1);
      }
      else if (timeFrame.compareTo("day") == 0) {
        numberLabels =
            numberLabels.concat(xmlStartTime.getMonth() + "-" + xmlStartTime.getDay() + "|");

        xmlStartTime = Tstamp.incrementDays(xmlStartTime, 1);
      }
      else {
        numberLabels =
            numberLabels.concat(xmlStartTime.getMonth() + "-" + xmlStartTime.getDay() + "|");

        xmlStartTime = Tstamp.incrementDays(xmlStartTime, 7);
      }
    }
    output = output.concat(numberLabels);

    output =
        output.concat(String.format("1:|0|%1$.0f|%2$.0f|%3$.0f|%4$.0f", currentMax * 0.25,
            currentMax * 0.5, currentMax * 0.75, currentMax));

    // Add the scale to the chart.
    output = output.concat("&amp;chds=");
    String scaleString = String.format("0,%1$.0f", currentMax);
    output = output.concat(scaleString);
    // If there was more than one source graphed,
    for (int counter = 1; counter < sourcesToGraph.size(); counter++) {
      // Have those numbers go by the same scale as all the other graphs.
      output = output.concat("," + scaleString);
    }

    // Add the data to the chart.
    output = output.concat(dataList);

    // Check to see if the URL for the output is over the current Google Charts maximum.
    if (output.length() > maxUrlSize) {
      return overflowImage;
    }

    return output;
  }
}
